#ifndef CALC_CORE_H
#define CALC_CORE_H

#include <algorithm>
#include <cstdlib>
#include <exception>
#include <functional>
#include <map>
#include <qobject.h>
#include <qtypes.h>
#include <stack>
#include <string>
#include <variant>

#include <QString>

static std::map<char, short> _priority_map = {
    { '+', 10 }, { '-', 10 }, { '*', 20 }, { '/', 20 }, { '{', 1 }, { '}', 1 }, { '!', 30 },
};

static std::map<char, std::function<double(double, double)>> _operator_calc_func = {
    { '+', [](double a, double b) { return a + b; } },
    { '-', [](double a, double b) { return a - b; } },
    { '*', [](double a, double b) { return a * b; } },
    { '/', [](double a, double b) { return a / b; } },
};

class ExpressionParser {
public:
    static void parse(QString& expression, std::variant<double, QString>& result) {
        std::string        exp_str = expression.toStdString();
        std::stack<char>   operator_stack;
        std::stack<double> number_stack;
        const int          exp_len = exp_str.size();

        if (exp_len <= 0) {
            result = QString("Error: empty expression");
            return;
        }

        ushort last_token = 0xff00;
        // 为了节约内存，last_token储存两个信息，第一个字节储存token类型，第二个字节储存操作符字符值
        // 0xff: 未知标识类型
        //    1: 数字，第二个字节用来标识正负，0为正，非0为负
        //    2: 操作符，同时第二个字节为操作符字符值

        for (int current_index = 0; current_index < exp_len; current_index++) {
            char   current_char = exp_str[current_index];
            ushort token_type   = 0x0000;
            // 描述同last_token

            // 解析是否为数字
            // 如果遇到'-'，先行判断是数字负号还是运算减号
            if (current_char == '-'
                && ((last_token == 0xff00)
                    || ((last_token & 0x0200) && (last_token & 0x00ff) == '('))) {
                char next_avaible_char = get_next_available_char(exp_str, current_index);
                if (is_digital(next_avaible_char)) {
                    token_type = 0x0101;
                }
                else if (next_avaible_char == '(') {
                    token_type = 0x0200 + '!';
                    last_token = token_type;
                    operator_stack.push('!');
                    continue;
                }
            }
            if ((token_type & 0x0100) == 0x0100 || is_digital(current_char)) {
                double number = token_type >> 8 == 1 ? 0.0 : current_char - '0';
                int    start  = token_type >> 8 == 1 ? current_index + 1 : current_index;
                int    iter   = start;
                for (; iter < exp_len; iter++) {
                    if (!(is_digital(exp_str[iter]) || exp_str[iter] == '.')) {
                        break;
                    }
                }

                if (iter - current_index <= 0) {
                    result = QString("Error: invalid syntax");
                    return;
                }
                else {
                    std::string num_str =
                        exp_str.substr(start, iter - start + (is_digital(exp_str[iter]) ? 1 : 0));

                    try {
                        if (std::count(num_str.begin(), num_str.end(), '.') > 1) {
                            result = QString("Error: illegal format for fraction");
                            return;
                        }
                        number = std::stod(num_str) * (token_type & 0x00ff ? -1 : 1);
                    }
                    catch (std::exception exp) {
                        result = QString("Error: illegal number format");
                        return;
                    }

                    current_index = is_digital(iter) ? iter : iter - 1;
                    number_stack.push(number);
                    last_token = 0x0100 + (token_type & 0x00ff ? 1 : 0);
                    continue;
                }
            }
            else if ((token_type & 0x0000) == 0x0000) {
                bool is_set = false;
                switch (current_char) {
                    case '+':
                    case '-':
                    case '*':
                    case '/': {
                        while (!is_set) {
                            if (operator_stack.empty()) {
                                operator_stack.push(current_char);
                                last_token = 0x0200 + current_char;
                                is_set     = true;
                                continue;
                            }

                            char last_operator = operator_stack.top();

                            if (!compare_priority(current_char, last_operator)) {
                                double a, b, ans;
                                if (last_operator == '!') {
                                    if (number_stack.size() < 1) {
                                        result = QString("Error: missing number after '-'");
                                        return;
                                    }
                                    operator_stack.pop();
                                    a = number_stack.top();
                                    number_stack.pop();
                                    ans = -1 * a;
                                    number_stack.push(ans);
                                    continue;
                                }
                                if (number_stack.size() < 2) {
                                    result = QString("Error: missing number(s) to calculate");
                                    return;
                                }
                                b = number_stack.top();
                                number_stack.pop();
                                a = number_stack.top();
                                number_stack.pop();
                                if (last_operator == '/' && b == 0.0) {
                                    result = QString("Error: divided by zero");
                                    return;
                                }
                                else {
                                    ans = _operator_calc_func[last_operator](a, b);
                                    number_stack.push(ans);
                                    operator_stack.pop();
                                }
                            }
                            else {
                                operator_stack.push(current_char);
                                last_token = (2 << 8) + current_char;
                                is_set     = true;
                            }
                        }
                        break;
                    }

                    case '(': {
                        operator_stack.push(current_char);
                        last_token = 0x0200 + current_char;
                        break;
                    }
                    case ')': {
                        while (!is_set) {
                            if (operator_stack.empty()) {
                                result = QString("Error: invalid syntax");
                                return;
                            }

                            char last_operator = operator_stack.top();

                            if (last_operator == '(') {
                                operator_stack.pop();
                                last_token = 0x0200 + current_char;
                                is_set     = true;
                                continue;
                            }

                            if (!compare_priority(current_char, last_operator)) {
                                double a, b, ans;
                                if (last_operator == '!') {
                                    if (number_stack.size() < 1) {
                                        result = QString("Error: missing number after '-'");
                                        return;
                                    }
                                    operator_stack.pop();
                                    a = number_stack.top();
                                    number_stack.pop();
                                    ans = -1 * a;
                                    number_stack.push(ans);
                                    continue;
                                }

                                if (number_stack.size() < 2) {
                                    result = QString("Error: missing number(s) to calculate");
                                    return;
                                }
                                b = number_stack.top();
                                number_stack.pop();
                                a = number_stack.top();
                                number_stack.pop();
                                if (last_operator == '/' && b == 0.0) {
                                    result = QString("Error: divided by zero");
                                    return;
                                }
                                else {
                                    ans = _operator_calc_func[last_operator](a, b);
                                    number_stack.push(ans);
                                    operator_stack.pop();
                                }
                            }
                            else {
                                operator_stack.push(current_char);
                                last_token = (2 << 8) + current_char;
                                is_set     = true;
                            }
                        }
                        break;
                    }

                    case ' ': {
                        is_set = true;
                        break;
                    }
                    default: {
                        is_set = true;
                        result = QString("Error: illegal character");
                        return;
                    }
                }
            }
        }

        while (!operator_stack.empty()) {
            char oper = operator_stack.top();
            operator_stack.pop();

            if (oper == '(' || oper == ')') {
                result = QString("Error: left brackets unpaired");
                return;
            }

            double a, b, ans;
            if (oper == '!') {
                if (number_stack.size() < 1) {
                    result = QString("Error: missing number after '-'");
                    return;
                }
                a = number_stack.top();
                number_stack.pop();
                ans = -1 * a;
                number_stack.push(ans);
                continue;
            }

            if (number_stack.size() < 2) {
                result = QString("Error: missing number(s) to calculate");
                return;
            }
            b = number_stack.top();
            number_stack.pop();
            a = number_stack.top();
            number_stack.pop();

            if (oper == '/' && b == 0.0) {
                result = QString("Error: divided by zero");
                return;
            }
            ans = _operator_calc_func[oper](a, b);
            number_stack.push(ans);
        }

        result = number_stack.top();
    }

private:
    static char get_char_by_offset(std::string& str, int start, int offset) {
        return start + offset >= 0 && start + offset < str.size() ? str[start + offset] : '\0';
    }

    static char get_next_available_char(std::string& str, int start) {
        char ch = '\0';
        for (int i = start + 1; i < str.size(); i++) {
            if (i != ' ') {
                ch = str[i];
                break;
            }
        }
        return ch;
    }

    // 若a的优先级高于b，则返回true，否则为false
    static bool compare_priority(char a, char b) {
        return _priority_map[a] > _priority_map[b];
    }

    static bool is_digital(char ch) {
        return '0' <= ch && ch <= '9';
    }
};

#endif  // CALC_CORE_H
